package project3;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JPanel;

/** GUI Class for display document related data. This class is specifically
 * used for visualization of an entire collection of data.
 */
public class GridDisplay extends JPanel
{
    double ROWS, COLS;
    Map<Document,Point2D.Double> documentMap;
    Color backgroundColor = Color.RED.darker().darker();
    Color lineColor = Color.YELLOW.darker().darker();

    /** Build an empty grid display */
    public GridDisplay()
    {
        ROWS = 15;
        COLS = 20;
        documentMap=new HashMap<Document,Point2D.Double>();
    }

    /** Build a grid display showing the passed in data */
    public GridDisplay(Map<Document, Point2D.Double> data)
    {
        this();
        documentMap=data;
    }

    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);
        drawBackground(g);

        //If a Query was performed (ie. the Map is no longer null), Plot the relation of documents
        if(documentMap==null)
            return;

        Point2D.Double minPoint=new Point2D.Double();
        Point2D.Double maxPoint=new Point2D.Double();
        findMinMaxPoints(minPoint, maxPoint);

        for(Map.Entry<Document, Point2D.Double> entry : documentMap.entrySet())
            drawDocument(g, entry.getKey(), entry.getValue(), minPoint, maxPoint);
        for(Map.Entry<Document, Point2D.Double> entry : documentMap.entrySet())
            drawPoint(g, entry.getValue(), minPoint, maxPoint, Color.CYAN);
        
        double midX = minPoint.x + (maxPoint.x-minPoint.x)/2;
        double midY = minPoint.y + (maxPoint.y-minPoint.y)/2;
        drawTag(g,"# of Stems (x axis)",new Point2D.Double(midX, minPoint.y), minPoint, maxPoint);
        drawTag(g,"# of Bytes (y axis)",new Point2D.Double(minPoint.x, midY), minPoint, maxPoint);
        String stemInfo="#Stems: {"+minPoint.x+","+maxPoint.x+"}  ";
        drawTag(g,stemInfo,new Point2D.Double(midX, maxPoint.y), minPoint, maxPoint);
        String sizeInfo="#Bytes: {"+minPoint.y+","+maxPoint.y+"}  ";
        drawTag(g,sizeInfo,new Point2D.Double(maxPoint.x, midY), minPoint, maxPoint);
     }

    final int SIZE = 10;
    final int CIRCLEBUFFER = 10;
    final int TEXTXLENGTHBUFFER = 5;
    final int TEXTXBUFFER = 5;
    final int TEXTYLENGTHBUFFER = 25;
    final int TEXTYBUFFER = 12;

    /** Draws a document to screen, as a point with doc title near by. */
    protected void drawDocument(Graphics g, Document doc, Point2D.Double location, Point2D.Double minPoint, Point2D.Double maxPoint)
    {
    	drawString(g, doc.getTitle(), location, minPoint, maxPoint, Color.CYAN);
    }

    /** Draws a data tag to screen. Has a color distinct from documents */
    protected void drawTag(Graphics g, String text, Point2D.Double location, Point2D.Double minPoint, Point2D.Double maxPoint)
    {
    	drawString(g, text, location, minPoint, maxPoint, Color.YELLOW);
    }

    /** Draws a string with associated dot to screen,
     * at the given location, relative to the given mins and maxes
     */
    protected void drawString(Graphics g, String s, Point2D.Double location, Point2D.Double minPoint, Point2D.Double maxPoint, Color tagColor)
    {
        double w=getWidth();
        double h=getHeight();
        // (x,y) would represent the document, (textx,texty) represents the title of the document
        double centerX = w*(location.x-minPoint.x)/(maxPoint.x-minPoint.x);
        double centerY = h*(location.y-minPoint.y)/(maxPoint.y-minPoint.y);
        double x = centerX - CIRCLEBUFFER;
        double y = centerY - CIRCLEBUFFER;
        double textX = centerX-TEXTXBUFFER;
        double textY = centerY-TEXTYBUFFER;
        //Handle the special case where the buffer makes x <= 0
        if(x<=0){
            x = 0;
            textX = 0;
        }
        //Handle the special case where the buffer makes y <= 0
        if(y<=0){
            y = 0;
            textY = 0;
        }
        //create the circle representing the document
        g.setColor(tagColor);
        g.fillOval((int)x,(int)y, SIZE,SIZE);

        //Make sure the text wont go off screen in the x direction
        if(x+s.length()*TEXTXLENGTHBUFFER > w){
            textX = (int) (textX - s.length()*TEXTXLENGTHBUFFER);
        }
        //Make sure the text wont go off screen in the y direction
        if(y<(SIZE*2)){
            textY = (int) (textY + TEXTYLENGTHBUFFER);
        }
        //draw over any backgroud obstructions (other text, lines, etc)
        Rectangle2D rect = g.getFontMetrics().getStringBounds(s, g);
        g.setColor(backgroundColor);
        g.fillRect((int)(textX+rect.getMinX()), (int)(textY+rect.getMinY()), (int)rect.getWidth(), (int)rect.getHeight());

        //draw text
        g.setColor(tagColor);
        g.drawString(s, (int)textX, (int)textY);
    }

    /** Simple draw point implementation to refresh points for data which may have been overwritten
     * by text box backgrounds.
     */
    protected void drawPoint(Graphics g, Point2D.Double location, Point2D.Double minPoint, Point2D.Double maxPoint, Color color)
    {
        double w=getWidth();
        double h=getHeight();
        // (x,y) would represent the document, (textx,texty) represents the title of the document
        double centerX = w*(location.x-minPoint.x)/(maxPoint.x-minPoint.x);
        double centerY = h*(location.y-minPoint.y)/(maxPoint.y-minPoint.y);
        double x = centerX - CIRCLEBUFFER;
        double y = centerY - CIRCLEBUFFER;
        if(x<=0) x = 0;
        //Handle the special case where the buffer makes y <= 0
        if(y<=0) y = 0;

        //create the circle representing the document
        g.setColor(color);
        g.fillOval((int)x,(int)y, SIZE,SIZE);
    }



    /** Goes through each point in the document map, and determines the min
     * and max individual x and y values; basically finding the bounding rectangle.
     *
     * NOTE that minPoint and maxPoint will be modified by this function.
     */
    private void findMinMaxPoints(Point2D.Double minPoint, Point2D.Double maxPoint)
    {
        minPoint.x=minPoint.y=Double.MAX_VALUE;
        maxPoint.x=maxPoint.y=Double.NEGATIVE_INFINITY;
        for(Point2D.Double point : documentMap.values())
        {
            minPoint.x=Math.min(minPoint.x, point.x);
            minPoint.y=Math.min(minPoint.y, point.y);
            maxPoint.x=Math.max(maxPoint.x, point.x);
            maxPoint.y=Math.max(maxPoint.y, point.y);
        }
    }

    /** Draws a grid line background for the visualization */
    protected void drawBackground(Graphics g)
    {
        double w=getWidth();
        double h=getHeight();
        g.setColor(backgroundColor);
        g.fillRect(0,0,(int)w,(int)h);
        g.setColor(lineColor);
        double dx=w/COLS;
        double dy=h/ROWS;
        for(double x=0; x<w; x+=dx)
        	g.drawLine((int)x, 0, (int)x, (int)h);
        for(double y=0; y<h; y+=dy)
        	g.drawLine(0, (int)y, (int)w, (int)y);
    }

    /** Change the data being drawn */
    public void setMap(Map<Document,Point2D.Double> data)
    {
        documentMap = data;
    }
}
